package com.googlecode.marrowboy;

import java.io.File;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;

import com.googlecode.marrowboy.assertionresult.TestRunDetail;
import com.googlecode.marrowboy.writers.OutputConfiguration;
import com.googlecode.marrowboy.writers.ReportWriter;
import com.googlecode.marrowboy.writers.Templates;
import com.googlecode.marrowboy.writers.VelocityTemplateWriter;

public class TestLogger {

    private final TestRunDetail testRunDetail = new TestRunDetail();

    private List<OutputConfiguration> outputConfigurations = new ArrayList<OutputConfiguration>();

    public TestLogger(List<File> outputFiles) {
        addConfiguredOutputConfigurations(outputFiles);
    }

    private void addConfiguredOutputConfigurations(List<File> outputFiles) {

        for (File thisOutputFile : outputFiles) {

            createParentDirs(thisOutputFile);

            String template = getTemplateByFileExtension(thisOutputFile);

            outputConfigurations.add(
                        new OutputConfiguration(
                                new VelocityTemplateWriter(template),
                                thisOutputFile));

        }

    }

    private void createParentDirs(File thisOutputFile) {
        thisOutputFile.getParentFile().mkdirs();
    }

    private String getTemplateByFileExtension(File thisOutputFile) {
        String extension = fileExtension(thisOutputFile);
        String template = getTemplateFor(extension);
        return template;
    }

    private String getTemplateFor(String extension) {
        String template = Templates.DEFAULT_PLAIN_TEXT_TEMPLATE;

        if ("xml".equals(extension)) {
            template = Templates.DEFAULT_XML_TEMPLATE;
        } else if ("html".equals(extension)) {
            template = Templates.DEFAULT_HTML_TEMPLATE;
        }
        return template;
    }

    private String fileExtension(File thisOutputFile) {
        String fileName = thisOutputFile.getName();
        String extension = fileName.substring(fileName.lastIndexOf('.') + 1);
        return extension;
    }

    public void writeAllOutputs() {
        for (final OutputConfiguration thisConfig : outputConfigurations) {
            System.out.println("[INFO] MarrowBoy report file: " + thisConfig.getOutputFileName());
            writeOutput(thisConfig);
        }
    }

    private void writeOutput(final OutputConfiguration thisConfig) {
        if (isValidOutputConfiguration(thisConfig)) {
            thisConfig.getReportWriter().writeReport(testRunDetail, thisConfig.getPrintStream());
        }
    }

    private boolean isValidOutputConfiguration(final OutputConfiguration thisConfig) {
        return thisConfig.getReportWriter() != null && thisConfig.getPrintStream() != null;
    }

    /**
     * Allows users to specify the {@link ReportWriter} to use (eg
     * {@link PlainTextWriter}), and the {@link PrintStream} to write to.
     * 
     * If either of these is null, the report will not be written anywhere. If
     * this method is called more than once, the last call will overwrite all
     * previous calls.
     * 
     * @param templateName
     *            the template to use. MarrowBoy will attempt to find a file
     *            with this name on the classpath.
     * 
     * @param printStream
     *            the {@link PrintStream} to write to. Easy options for this
     *            argument are {@link System#out} or
     *            {@link PrintStream#PrintStream(String)}
     */
    public void addReportTemplate(final String templateName, final PrintStream printStream) {
        outputConfigurations.add(new OutputConfiguration(new VelocityTemplateWriter(templateName), printStream));
    }

    /**
     * Remove all current {@link OutputConfiguration}s
     */
    public void clearReportWriters() {
        outputConfigurations.clear();
    }

    public void logAssertion(String className, String methodName, String reason, Object actual, Matcher<?> matcher, boolean wasSuccessful) {

        final String actualString = createDescriptiveString(actual);
        String descriptionString = createDescription(matcher);

        testRunDetail.addAssertion(className, methodName, reason, actualString, descriptionString, wasSuccessful);

    }

    private String createDescription(Matcher<?> matcher) {
        final Description description = new StringDescription();
        matcher.describeTo(description);
        return description.toString();
    }

    private String createDescriptiveString(final Object actual) {

        // TODO: try to do other types nicely here

        final String actualString;

        if (actual == null) {
            actualString = "null";
        } else {
            actualString = actual.toString();
        }
        return actualString;
    }

}
